/*
 * PDLogger
 * 
 * Copyright (c) 2012, Zoujie Wu <yibn2008@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 *   * Redistributions of source code must retain the above copyright notice, this 
 *     list of conditions and the following disclaimer.
 *   
 *   * Redistributions in binary form must reproduce the above copyright notice, 
 *     this list of conditions and the following disclaimer in the documentation 
 *     and/or other materials provided with the distribution.
 *   
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* $Id$ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>

#ifdef PHP_WIN32
#include "win32/time.h"
#else
#include <sys/time.h>
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_pdlogger.h"
#include "pdl_record.h"
#include "pdl_output.h"
#include "pdl_override.h"
#include "pdl_watch.h"

/* declare pdlogger globals */
ZEND_DECLARE_MODULE_GLOBALS(pdlogger)

static PHP_GINIT_FUNCTION(pdlogger);
static PHP_GSHUTDOWN_FUNCTION(pdlogger);

/* {{{ pdlogger_functions[]
 */
const zend_function_entry pdlogger_functions[] = {
	/* comment functions */
	PHP_FE(pdl_start, pdl_start_arginfo)
	PHP_FE(pdl_started, NULL)
	PHP_FE(pdl_stop, pdl_stop_arginfo)
	PHP_FE(pdl_reset, NULL)
	PHP_FE(pdl_enable, pdl_enable_arginfo)

	/* record functions */
	PHP_FE(pdl_info, pdl_info_arginfo)
	PHP_FE(pdl_warning, pdl_warning_arginfo)
	PHP_FE(pdl_error, pdl_error_arginfo)
	PHP_FE(pdl_exception, pdl_exception_arginfo)
	PHP_FE(pdl_dump, pdl_dump_arginfo)
	PHP_FE(pdl_records_num, NULL)

	/* output functions */
	PHP_FE(pdl_format, pdl_format_arginfo)
	PHP_FE(pdl_format_handler, pdl_format_handler_arginfo)
	PHP_FE(pdl_export, NULL)
	PHP_FE(pdl_write, NULL)

	/* watch functions */
	PHP_FE(pdl_watch, pdl_watch_arginfo)
	PHP_FE(pdl_watch_func, pdl_watch_func_arginfo)

	PHP_FE_END
};
/* }}} */

/* {{{ pdlogger_module_entry
 */
zend_module_entry pdlogger_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
	STANDARD_MODULE_HEADER,
#endif
	"pdlogger",
	pdlogger_functions,
	PHP_MINIT(pdlogger),
	PHP_MSHUTDOWN(pdlogger),
	PHP_RINIT(pdlogger),
	PHP_RSHUTDOWN(pdlogger),
	PHP_MINFO(pdlogger),
#if ZEND_MODULE_API_NO >= 20010901
	PDL_VERSION,
#endif
	PHP_MODULE_GLOBALS(pdlogger),
	PHP_GINIT(pdlogger),
	PHP_GSHUTDOWN(pdlogger),
	NULL,
	STANDARD_MODULE_PROPERTIES_EX
};
/* }}} */

#ifdef COMPILE_DL_PDLOGGER
ZEND_GET_MODULE(pdlogger)
#endif

PHP_INI_MH(OnUpdatePdlPeriod)
{
	int period = atoi(new_value);
    if (period > 0) {
    	PDL_G(period) = period;
        return SUCCESS;
    }
    return FAILURE;
}

/* {{{ PHP_INI
 */
PHP_INI_BEGIN()
    STD_PHP_INI_BOOLEAN("pdlogger.enable", "0", PHP_INI_ALL, OnUpdateBool, enable, zend_pdlogger_globals, pdlogger_globals)
    STD_PHP_INI_ENTRY("pdlogger.format", "4", PHP_INI_ALL, OnUpdateLong, format, zend_pdlogger_globals, pdlogger_globals)
    STD_PHP_INI_ENTRY("pdlogger.watch", "31", PHP_INI_ALL, OnUpdateLong, watch, zend_pdlogger_globals, pdlogger_globals)
    STD_PHP_INI_ENTRY("pdlogger.output_dir", "",  PHP_INI_ALL, OnUpdateString, output_dir, zend_pdlogger_globals, pdlogger_globals)
    STD_PHP_INI_ENTRY("pdlogger.log_ext", "",  PHP_INI_ALL, OnUpdateString, log_ext, zend_pdlogger_globals, pdlogger_globals)
    STD_PHP_INI_ENTRY("pdlogger.period", "86400",  PHP_INI_ALL, OnUpdatePdlPeriod, period, zend_pdlogger_globals, pdlogger_globals)
PHP_INI_END()
/* }}} */

/* {{{ php_pdlogger_init_globals
 */
static void php_pdlogger_init_globals(zend_pdlogger_globals *pdlogger_globals)
{
	pdlogger_globals->enable			= 0;
	pdlogger_globals->format			= PDL_FM_TEXT;
	pdlogger_globals->watch				= PDL_W_ALL;
	pdlogger_globals->output_dir		= NULL;
	pdlogger_globals->log_ext			= NULL;
	pdlogger_globals->period			= PDL_DEFAULT_PERIOD;
}
/* }}} */

/* {{{ php_pdlogger_init_globals
 */
static PHP_GINIT_FUNCTION(pdlogger)
{
	HashTable	*ht = &pdlogger_globals->override_funcs;

	zend_hash_init(ht, PDL_INIT_FUNC_NUM, NULL, NULL, 1);

	/* add pre-defined override functions */
	PDL_ADD_O_METHOD(ht, PDO, __construct);
	PDL_ADD_O_METHOD(ht, PDO, prepare);
	PDL_ADD_O_METHOD(ht, PDO, query);
	PDL_ADD_O_METHOD(ht, PDO, exec);
	PDL_ADD_O_METHOD(ht, PDO, beginTransaction);
	PDL_ADD_O_METHOD(ht, PDO, commit);
	PDL_ADD_O_METHOD(ht, PDO, rollBack);

	pdlogger_globals->started			= 0;
	pdlogger_globals->has_written		= 0;
	pdlogger_globals->start_time.sec	= 0;
	pdlogger_globals->start_time.usec	= 0;
}
/* }}} */

static PHP_GSHUTDOWN_FUNCTION(pdlogger)
{
	if (!pdlogger_globals) {
		return;
	}

	zend_hash_destroy(&pdlogger_globals->override_funcs);
}

/* {{{ PHP_MINIT_FUNCTION
 */
PHP_MINIT_FUNCTION(pdlogger)
{
	REGISTER_INI_ENTRIES();

	REGISTER_LONG_CONSTANT("PDL_LV_INFO", 		PDL_LV_INFO, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("PDL_LV_WARNING", 	PDL_LV_WARNING, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("PDL_LV_ERROR", 		PDL_LV_ERROR, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("PDL_LV_EXCEPTION", 	PDL_LV_EXCEPTION, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("PDL_LV_DEBUG", 		PDL_LV_DEBUG, CONST_PERSISTENT | CONST_CS);

	REGISTER_LONG_CONSTANT("PDL_FM_CUSTOM", 	PDL_FM_CUSTOM, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("PDL_FM_XML", 		PDL_FM_XML, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("PDL_FM_TEXT", 		PDL_FM_TEXT, CONST_PERSISTENT | CONST_CS);

	REGISTER_LONG_CONSTANT("PDL_W_ERROR", 		PDL_W_ERROR, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("PDL_W_LOAD", 		PDL_W_LOAD, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("PDL_W_PDO",			PDL_W_PDO, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("PDL_W_FUNC", 		PDL_W_FUNC, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("PDL_W_STATUS",		PDL_W_STATUS, CONST_PERSISTENT | CONST_CS);
	REGISTER_LONG_CONSTANT("PDL_W_ALL", 		PDL_W_ALL, CONST_PERSISTENT | CONST_CS);

	REGISTER_STRINGL_CONSTANT("PDL_VERSION", 	PDL_VERSION, sizeof(PDL_VERSION) - 1, CONST_PERSISTENT | CONST_CS);

	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MSHUTDOWN_FUNCTION
 */
PHP_MSHUTDOWN_FUNCTION(pdlogger)
{
	UNREGISTER_INI_ENTRIES();

	return SUCCESS;
}
/* }}} */

/* {{{ PHP_RINIT_FUNCTION
 */
PHP_RINIT_FUNCTION(pdlogger)
{
	pdl_cf_cb_t	cf = {NULL, NULL, NULL, 0};

	zend_hash_init(&PDL_G(watch_funcs), PDL_INIT_FUNC_NUM, NULL, (dtor_func_t) pdl_wf_dtor, 0);
	zend_hash_init(&PDL_G(records), PDL_INIT_RECORD_NUM, NULL, (dtor_func_t) pdl_record_dtor, 0);

	PDL_G(cf_cb) = cf;
	PDL_G(started) = 0;
	PDL_G(has_written) = 0;
	PDL_G(start_time) = pdl_get_curr_time();

	return SUCCESS;
}
/* }}} */

/* {{{ PHP_RSHUTDOWN_FUNCTION
 */
PHP_RSHUTDOWN_FUNCTION(pdlogger)
{
	if (PDL_G(started)) {
		pdl_stop(1 TSRMLS_CC);
	} else if (!PDL_G(has_written)) {
		pdl_write_records(TSRMLS_C);
		PDL_G(has_written) = 1;
	}

	zend_hash_destroy(&PDL_G(records));
	zend_hash_destroy(&PDL_G(watch_funcs));

	PDL_CLEAN_CF_CB(PDL_G(cf_cb));

	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MINFO_FUNCTION
 */
PHP_MINFO_FUNCTION(pdlogger)
{
	php_info_print_table_start();
	php_info_print_table_header(2, "pdlogger support", "enabled");
	php_info_print_table_row(2, "Version", PDL_VERSION);
	php_info_print_table_end();

	DISPLAY_INI_ENTRIES();
}
/* }}} */

/**
 * start PDLogger
 * 
 * @param  zend_bool is reset the records (clean it)
 * @return boolean   is the PDLogger started
 */
PDL_API zend_bool pdl_start(zend_bool reset TSRMLS_DC)
{
	if (PDL_G(enable) && !PDL_G(started)) {
		if (reset) {
			zend_hash_clean(&PDL_G(records));
		}

		PDL_G(started) = 1;
		pdl_register_watch_hooks(TSRMLS_C);
		pdl_replace_override_funcs(1 TSRMLS_CC);
	}

	return PDL_G(started);
}

/**
 * stop PDLogger
 */
PDL_API void pdl_stop(zend_bool write TSRMLS_DC)
{
	if (PDL_G(started)) {
		PDL_G(started) = 0;
		pdl_unregister_watch_hooks(TSRMLS_C);
		pdl_replace_override_funcs(0 TSRMLS_CC);

		if (write && !PDL_G(has_written)) {
			PDL_G(has_written) = 1;
			pdl_write_records(TSRMLS_C);
		}
	} else {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to stop logging before calling pdl_start()");
	}
}

/**
 * pdl_record_t destructor
 */
PDL_API void pdl_record_dtor(pdl_record_t **record_p)
{
	pdl_record_t *record = *record_p;

	if (record) {
		efree(record->label);
		efree(record->msg);
		if (record->data) {
			zval_ptr_dtor(&(record->data));
		}
		efree(record->file);
		efree(record);
	}
}

PDL_API void pdl_wf_dtor(pdl_wf_t *wf)
{
	if (wf->class_name) {
		efree(wf->class_name);
	}
	efree(wf->func_name);
	if (wf->watch_cb) {
		efree(wf->watch_cb);
	}
}

/**
 * get the string name of log level
 */
PDL_API const char* pdl_level_name(int level)
{
	const char *name;
	
	switch (level) {
		case PDL_LV_INFO:
			name = "Info";
			break;
		case PDL_LV_WARNING:
			name = "Warning";
			break;
		case PDL_LV_ERROR:
			name = "Error";
			break;
		case PDL_LV_EXCEPTION:
			name = "Exception";
			break;
		case PDL_LV_DEBUG:
			name = "Debug";
			break;
		default:
			name = "Unknown";
	}

	return name;
}

/* get current time */
PDL_API pdl_time_t pdl_get_curr_time()
{
	pdl_time_t		t;
	struct timeval	tval;

	gettimeofday(&tval, NULL);

	t.sec = (long) tval.tv_sec;
	t.usec = (long) tval.tv_usec;

	return t;
}

/* calculate time interval */
PDL_API double pdl_calc_interval(pdl_time_t from)
{
	struct timeval tval;
	long sec, usec;

	gettimeofday(&tval, NULL);

	sec = tval.tv_sec - from.sec;
	usec = tval.tv_usec - from.usec;

	return (double) (sec + usec / MICRO_IN_SEC);
}

/* {{{ proto bool pdl_start([bool $reset = true])
 */
PHP_FUNCTION(pdl_start)
{
	zend_bool reset = 1;

	PDL_CHECK_ENABLE;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &reset) == FAILURE) {
		return;
	}

	if (pdl_start(reset TSRMLS_CC) == 1) {
		RETURN_TRUE;
	}
	
	RETURN_FALSE;
}
/* }}} */

/* {{{ proto bool pdl_started()
 */
PHP_FUNCTION(pdl_started)
{
	if (PDL_G(started) == 1) {
		RETURN_TRUE;
	}
	RETURN_FALSE;
}
/* }}} */

/* {{{ proto void pdl_stop([bool write = false])
 */
PHP_FUNCTION(pdl_stop)
{
	zend_bool	write = 0;

	PDL_CHECK_ENABLE;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &write) == FAILURE) {
		return;
	}

	pdl_stop(write TSRMLS_CC);
}
/* }}} */

/* {{{ proto long pdl_reset()
 */
PHP_FUNCTION(pdl_reset)
{
	RETVAL_LONG(zend_hash_num_elements(&PDL_G(records)));
	zend_hash_clean(&PDL_G(records));
}
/* }}} */

/* {{{ proto bool pdl_enable([bool $enable = true])
 */
PHP_FUNCTION(pdl_enable)
{
	zend_bool enable = 1;
	int argc = ZEND_NUM_ARGS();

	RETVAL_BOOL(PDL_G(enable));

	if (zend_parse_parameters(argc TSRMLS_CC, "|b", &enable) == FAILURE) {
		return;
	}

	if (argc > 0) {
		PDL_G(enable) = enable;
	}
}
/* }}} */

/* {{{ proto double pdl_start_time()
 */
PHP_FUNCTION(pdl_start_time)
{
	RETURN_DOUBLE((double) (PDL_G(start_time).sec + PDL_G(start_time).usec / MICRO_IN_SEC));
}
/* }}} */

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */
